package com.example.stargazer.usb_service;

import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.util.Log;
import android.widget.Toast;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;

/**
 * Created by StarGazer on 2015/11/21.
 */
public class USBDriver {

    final int  MAX_NUMBER_BYTES = 65536;

    private byte [] readBuffer;
    private byte [] usbData;
    private int writeIndex;
    private int readIndex;
    private int readCount;
    private int totalBytes;
    private ArrayList<String> deviceNum = new ArrayList<>();
    private int deviceCount=0;
    public int writeTimeOutMillis;
    public int readTimeOutMillis;
    private boolean broadcastFlag = false;
    String mString;
    private int bulkPacketSize;

    public boolean READ_ENABLE = false;

    protected final Object readQueueLock = new Object();
    protected final Object writeQueueLock = new Object();

    public  ReadThread readThread;
    private UsbManager usbManager;
    private UsbDevice usbDevice;
    public UsbInterface usbInterface;
    private Context context;
    private UsbDeviceConnection usbDeviceConnection;
    private PendingIntent pendingIntent;
    private UsbEndpoint bulkInPoint;
    private UsbEndpoint bulkOutPoint;

    public USBDriver(Context context, UsbManager usbManager){
        readBuffer = new byte [MAX_NUMBER_BYTES];
        usbData = new byte[1024];

        this.usbManager = usbManager;
        this.context = context;
        this.mString = "USB_service";

        writeTimeOutMillis = 10000;
        readTimeOutMillis = 10000;

        addDevice("1a86:7523");
    }

    private void addDevice(String str){
        deviceNum.add(str);
        deviceCount = deviceNum.size();
    }

    public synchronized void openUsbDevice(UsbDevice usbDevice){

        Object localObject;
        UsbInterface intf;
        if(usbDevice == null)
            return;
        intf = getUsbInterface(usbDevice);
        if(intf != null) {
            localObject = this.usbManager.openDevice(usbDevice);
            if(localObject != null) {
                if(((UsbDeviceConnection)localObject).claimInterface(intf, true)) {
                    this.usbDevice = usbDevice;
                    this.usbDeviceConnection = ((UsbDeviceConnection)localObject);
                    this.usbInterface = intf;
                    if(!enumerateEndPoint(intf))
                        return;
                    Toast.makeText(context, "Device Has Attached to Android", Toast.LENGTH_LONG).show();
                    if(!READ_ENABLE){
                        READ_ENABLE = true;
                        readThread = new ReadThread(bulkInPoint, usbDeviceConnection);
                        readThread.start();
                    }
                }
            }
        }
    }

    public synchronized void openDevice(UsbDevice usbDevice){
        pendingIntent = PendingIntent.getBroadcast(context, 0, new Intent(mString),0);
        if (usbManager.hasPermission(usbDevice)){
            openUsbDevice(usbDevice);
        }else {
            synchronized (usbReceiver){
                usbManager.requestPermission(usbDevice, pendingIntent);
            }
        }
    }

    public synchronized void closeDevice(){
        try{Thread.sleep(10);}
        catch(Exception e){
            e.printStackTrace();
        }

        if(this.usbDeviceConnection != null)
        {
            if(this.usbInterface != null) {
                this.usbDeviceConnection.releaseInterface(this.usbInterface);
                this.usbInterface = null;
            }

            this.usbDeviceConnection.close();
        }

        if(this.usbDevice != null) {
            this.usbDevice = null;
        }

        if(this.usbManager != null) {
            this.usbManager = null;
        }


        if(READ_ENABLE) {
            READ_ENABLE = false;
        }

		/*
		 * No need unregisterReceiver
		 */
        if(broadcastFlag) {
            this.context.unregisterReceiver(usbReceiver);
            broadcastFlag = false;
        }

//		System.exit(0);
    }


    public UsbDevice enumerateDevice(){

        usbManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
        pendingIntent = PendingIntent.getBroadcast(context, 0, new Intent(mString), 0);
        HashMap<String, UsbDevice> deviceList = usbManager.getDeviceList();
        if(deviceList.isEmpty()) {
            Toast.makeText(context, "No Device Or Device Not Match", Toast.LENGTH_LONG).show();
            return null;
        }
        for (UsbDevice localUsbDevice : deviceList.values()) {
            for (int i = 0; i < deviceCount; ++i) {
//				 Log.d(TAG, "DeviceCount is " + DeviceCount);
                if (String.format("%04x:%04x", new Object[]{localUsbDevice.getVendorId(),
                        localUsbDevice.getProductId()}).equals(deviceNum.get(i))) {
                    IntentFilter filter = new IntentFilter(mString);
                    filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
                    context.registerReceiver(usbReceiver, filter);
                    broadcastFlag = true;
                    return localUsbDevice;

                } else {
                    Log.d("", "String.format not match");
                }
            }
        }

        return null;
    }

    public boolean isConnected()
    {
        return (this.usbDevice != null) && (this.usbInterface != null) && (this.usbDeviceConnection != null);
    }

    //过滤器
    public UsbInterface getUsbInterface(UsbDevice usbDevice){

        if (this.usbDeviceConnection != null){
            if (this.usbInterface != null ){
                this.usbDeviceConnection.releaseInterface(this.usbInterface);
                this.usbInterface = null;
            }
            this.usbDeviceConnection.close();
            this.usbDevice = null;
            this.usbInterface = null;
        }

        if (usbDevice == null){
            return null;
        }

        for(int i=0;i< usbDevice.getInterfaceCount(); i++){

            UsbInterface intf = usbDevice.getInterface(i);
            if (intf.getInterfaceClass() == 0xff
                    && intf.getInterfaceSubclass() == 0x01
                    && intf.getInterfaceProtocol() == 0x02) {
                return intf;
            }
        }
        return null;
    }

    public boolean enumerateEndPoint(UsbInterface sInterface){
        if(sInterface == null)
            return false;
        for(int i = 0; i < sInterface.getEndpointCount(); ++i) {
            UsbEndpoint endPoint = sInterface.getEndpoint(i);
            if(endPoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK && endPoint.getMaxPacketSize() == 0x20) {
                if(endPoint.getDirection() == UsbConstants.USB_DIR_IN) {
                    bulkInPoint = endPoint;
                } else {
                    bulkOutPoint = endPoint;
                }
                this.bulkPacketSize = endPoint.getMaxPacketSize();
            }
        }
        return true;

    }

    /***********USB broadcast receiver*******************************************/
    private final BroadcastReceiver usbReceiver = new BroadcastReceiver()
    {
        @Override
        public void onReceive(Context context, Intent intent)
        {
            String action = intent.getAction();
            if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action))
                return;

            if(mString.equals(action))
            {
                synchronized(this)
                {
                    UsbDevice localUsbDevice = intent.getParcelableExtra("device");
                    if(intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false))
                    {
                        openUsbDevice(localUsbDevice);
                    } else {
                        Toast.makeText(USBDriver.this.context, "Deny USB Permission", Toast.LENGTH_SHORT).show();
                        Log.d("", "permission denied");
                    }
                }
            } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
                Toast.makeText(USBDriver.this.context, "Disconnect", Toast.LENGTH_SHORT).show();
                //CloseDevice();
            } else {
                Log.d("","......");
            }
        }
    };



    private class ReadThread extends Thread{
        UsbEndpoint usbEndpoint;
        UsbDeviceConnection usbDeviceConnection;

        public ReadThread(UsbEndpoint usbEndpoint, UsbDeviceConnection usbDeviceConnection){
            this.usbEndpoint = usbEndpoint;
            this.usbDeviceConnection = usbDeviceConnection;
        }

        @Override
        public void run() {
            while (READ_ENABLE){
                while (totalBytes > MAX_NUMBER_BYTES-63){
                    try {
                        Thread.sleep(5);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }

                synchronized (readQueueLock){
                    readCount = usbDeviceConnection.bulkTransfer(usbEndpoint, usbData, 64, readTimeOutMillis);
                    if(readCount > 0){
                        for(int count = 0; count< readCount; count++)
                        {
                            readBuffer[writeIndex] = usbData[count];
                            writeIndex++;
                            writeIndex %= MAX_NUMBER_BYTES;
                        }

                        if(writeIndex >= readIndex)
                            totalBytes = writeIndex-readIndex;
                        else
                            totalBytes = (MAX_NUMBER_BYTES-readIndex)+writeIndex;

                    }
                }
            }
        }
    }

    public int readData(byte[]data, int length)
    {
        int mLen;

		/*should be at least one byte to read*/
        if((length < 1) || (totalBytes == 0)){
            mLen = 0;
            return mLen;
        }

		/*check for max limit*/
        if(length > totalBytes)
            length = totalBytes;

		/*update the number of bytes available*/
        totalBytes -= length;

        mLen = length;

		/*copy to the user buffer*/
        for(int count = 0; count < length; count++)
        {
            data[count] = readBuffer[readIndex];
            readIndex++;
			/*shouldnt read more than what is there in the buffer,
			 * 	so no need to check the overflow
			 */
            readIndex %= MAX_NUMBER_BYTES;
        }
        return mLen;
    }



//写数据输出部分
    public int writeData(byte[] buf, int length) throws IOException{
        int mLen;
        mLen = writeData(buf, length, this.writeTimeOutMillis);
        if(mLen < 0)
        {
            throw new IOException("Expected Write Actual Bytes");
        }
        return mLen;
    }

    public int writeData(byte[] buf, int length, int timeoutMillis){
        int offset = 0;
        int HasWritten;
        int odd_len = length;
        if(this.bulkOutPoint == null)
            return -1;
        while(offset < length)
        {
            synchronized(this.writeQueueLock) {
                int mLen = Math.min(odd_len, this.bulkPacketSize);
                byte[] arrayOfByte = new byte[mLen];
                if(offset == 0) {
                    System.arraycopy(buf, 0, arrayOfByte, 0, mLen);
                } else {
                    System.arraycopy(buf, offset, arrayOfByte, 0, mLen);
                }
                HasWritten = this.usbDeviceConnection.bulkTransfer(this.bulkOutPoint, arrayOfByte, mLen, timeoutMillis);
                if(HasWritten < 0) {
                    return -2;
                } else {
                    offset += HasWritten;
                    odd_len -= HasWritten;
//					Log.d(TAG, "offset " + offset + " odd_len " + odd_len);
                }
            }
        }
        return offset;
    }
}
